package javanet.util;

import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Date;
import java.util.List;

import javanet.communication.ParameterWrapper;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.commons.betwixt.io.BeanReader;
import org.apache.commons.betwixt.io.BeanWriter;
import org.apache.commons.betwixt.strategy.DecapitalizeNameMapper;

/**
 * @author Markus Holzer
 */
public class SerializationUtil {

	@SuppressWarnings("unchecked")
	public static <T> T deserializeParameter(String xml, Class<T> type)
			throws Exception {
		System.out.println("deserializeParameter:\n" + xml);
		if (isTypeInstanceOfWrappedParameter(type)) {
			ParameterWrapper wrapper = deserializeParameterWithJAXB(xml, type);
			return (T) wrapper.getWrappedObject();
		}

		xml = getXMLWithLowerCaseTypes(xml);
		BeanReader beanReader = new BeanReader();

		beanReader.getBindingConfiguration().setObjectStringConverter(
				new JavaNETObjectStringConverter());
		String path = type.getSimpleName().substring(0, 1).toLowerCase()
				+ type.getSimpleName().substring(1);
		T returnObject = null;
		beanReader.registerBeanClass(path, type);
		returnObject = (T) beanReader.parse(new StringReader(xml));
		System.out.println("after deserialization: " + returnObject);
		return returnObject;
	}

	private static boolean isTypeInstanceOfWrappedParameter(@SuppressWarnings("rawtypes") Class type) {
		if (type == String.class)
			return true;
		if (type == Long.class)
			return true;
		if (type == Integer.class)
			return true;
		if (type == Boolean.class)
			return true;
		if (type == Date.class)
			return true;
		return false;
	}

	@SuppressWarnings("rawtypes")
	public static String serializeParameter(Object parameter, Class type)
			throws Exception {
		Writer outputWriter = new StringWriter();
		outputWriter.write("<?xml version='1.0' ?>\n");

		BeanWriter beanWriter = new BeanWriter(outputWriter);

		beanWriter.getXMLIntrospector().getConfiguration()
				.setAttributesForPrimitives(false);
		beanWriter.getXMLIntrospector().getConfiguration()
				.setIgnoreAllBeanInfo(true);
		beanWriter.getXMLIntrospector().getConfiguration()
				.setElementNameMapper(new DecapitalizeNameMapper());
		beanWriter.getBindingConfiguration().setMapIDs(false);
		beanWriter.getBindingConfiguration().setObjectStringConverter(
				new JavaNETObjectStringConverter());
		beanWriter.setWriteEmptyElements(false);
		beanWriter.enablePrettyPrint();
		if (parameter instanceof List && ((List) parameter).size() > 0) {
			String componentType = ((List) parameter).get(0).getClass()
					.getSimpleName();
			beanWriter.write("ArrayOf" + componentType, parameter);

		} else
			beanWriter.write(parameter);

		String xml = outputWriter.toString();
		outputWriter.close();
		System.out.println("serializeParameter:\n" + xml);
		return xml;
	}

	private static ParameterWrapper deserializeParameterWithJAXB(String xml,
			@SuppressWarnings("rawtypes") Class wrappedType) throws Exception {

		xml = xml.replace("Milestone", "milestone");
		xml = xml.replace("dateTime", "date");

		System.out.println("deserializing String: " + xml);
		JAXBContext context = JAXBContext.newInstance(ParameterWrapper.class,
				wrappedType);
		Unmarshaller unmarshaller = context.createUnmarshaller();
		ParameterWrapper wrapper = (ParameterWrapper) unmarshaller
				.unmarshal(new StringReader(xml));
		if (wrapper.getWrappedObject() instanceof XMLGregorianCalendar) {
			XMLGregorianCalendar cal = (XMLGregorianCalendar) wrapper
					.getWrappedObject();
			Object o = wrappedType.cast(cal.toGregorianCalendar().getTime());
			wrapper.setWrappedObject(o);
		}
		System.out.println(wrapper);
		return wrapper;
	}

	private static String getXMLWithLowerCaseTypes(String xml) {
		String ret = new String(xml);
		int index = 0;
		while (true) {
			index = xml.indexOf("<", index + 1);
			if (index == -1)
				break;
			ret = ret.substring(0, index + 1);
			if (xml.indexOf("/", index + 1) == index + 1) {
				ret += "/" + xml.substring(index + 2, index + 3).toLowerCase()
						+ xml.substring(index + 3);
			} else {
				ret += xml.substring(index + 1, index + 2).toLowerCase()
						+ xml.substring(index + 2);
			}
		}
		return ret;
	}

}
